package registry

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"net/url"
	"sync"
)

type serviceUpdateHandler struct{}

func (h *serviceUpdateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

	decoder := json.NewDecoder(r.Body)
	var p patch
	err := decoder.Decode(&p)
	if err != nil {
		log.Println(err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	fmt.Printf("Updated receieved %v\n", p)
	prov.Update(p)
}

func RegisterService(registration Registration) error {
	heartbeatURL, err := url.Parse(registration.HeartbeatURL)
	if err != nil {
		return err
	}
	http.HandleFunc(heartbeatURL.Path, func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	})

	serviceUpdateURL, err := url.Parse(registration.ServiceUpdateURL)
	if err != nil {
		return err
	}

	http.Handle(serviceUpdateURL.Path, &serviceUpdateHandler{})

	buf := new(bytes.Buffer)
	encoder := json.NewEncoder(buf)
	err = encoder.Encode(registration)
	if err != nil {
		return err
	}

	resp, err := http.Post(ServicesURL, "application/json", buf)
	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("failed to registry service, "+
			"RegistryService responded with code %v", resp.StatusCode)
	}

	return nil
}

func ShutdownService(serviceName ServiceName) error {
	body := bytes.NewBuffer([]byte(serviceName))
	req, err := http.NewRequest(http.MethodDelete, ServicesURL, body)
	if err != nil {
		return err
	}
	req.Header.Add("Content-Type", "text/plain")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("failed to deregister service, "+
			"RegistryService responded with code %v", resp.StatusCode)
	}

	return nil
}

type providers struct {
	services      map[ServiceName][]string
	servicesMutex *sync.RWMutex
}

func (p *providers) Update(patch patch) {
	p.servicesMutex.Lock()
	defer p.servicesMutex.Unlock()

	for _, patchEntry := range patch.Added {
		_, ok := p.services[patchEntry.Name]
		if !ok {
			p.services[patchEntry.Name] = make([]string, 0)
		}
		p.services[patchEntry.Name] = append(p.services[patchEntry.Name],
			patchEntry.URL)
	}

	for _, patchEntry := range patch.Removed {
		providerURLs, ok := p.services[patchEntry.Name]
		if ok {
			for i := range providerURLs {
				if providerURLs[i] == patchEntry.URL {
					p.services[patchEntry.Name] = append(
						providerURLs[:i], providerURLs[i+1:]...)
				}
			}
		}
	}
}

func (p *providers) getRandomOne(name ServiceName) (string, error) {
	providers, err := p.get(name)
	if err != nil {
		return "", err
	}
	idx := int(rand.Float32() * float32(len(providers)))
	return providers[idx], nil
}

func (p *providers) get(name ServiceName) ([]string, error) {
	providers, ok := p.services[name]
	if !ok {
		return nil, fmt.Errorf("No providers available for service %v", name)
	}
	return providers, nil
}

func GetProvider(name ServiceName) (string, error) {
	return prov.getRandomOne(name)
}

var prov = providers{
	services:      make(map[ServiceName][]string),
	servicesMutex: new(sync.RWMutex),
}
